/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.util.core;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.webapp.taglib.core.grid.GridTag;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.GenericValidator;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;


/**
 * <p>RequestUtil utility class Good ol' copy-n-paste from  <a
 * href="http://www.javaworld.com/javaworld/jw-02-2002/ssl/utilityclass.txt">
 * http://www.javaworld.com/javaworld/jw-02-2002/ssl/utilityclass.txt</a>
 * which is referenced in the following article: <a
 * href="http://www.javaworld.com/javaworld/jw-02-2002/jw-0215-ssl.html">
 * http://www.javaworld.com/javaworld/jw-02-2002/jw-0215-ssl.html</a>
 * </p>
 * Added some methods by Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.9 $ $Date: 2006/08/20 14:39:43 $
 */
public class RequestUtil {
	private static final String STOWED_REQUEST_ATTRIBS = "ssl.redirect.attrib.stowed";
	protected transient final Log log = LogFactory.getLog(RequestUtil.class);


	/**
	 * Creates query String from request body parameters
     *
     * @param request The request that will supply parameters
     * @return Query string corresponding to that request parameters
	 */
	public static String getRequestParameters(HttpServletRequest request) {
		// set the ALGORIGTHM as defined for the application
		//ALGORITHM = (String) aRequest.getAttribute(Constants.ENC_ALGORITHM);
		Map m = request.getParameterMap();
		return createQueryStringFromMap(m, "&").toString();
	}


	/**
	 * Creates map of request parameters from given query string. Values are
     * decoded.
	 *
	 * @param queryString Query string to get parameters from
	 * @return Map with request parameters mapped to their values
	 */
	public static Map getRequestParametersFromString(String queryString) {
        return getRequestParametersFromString(queryString, true);
    }


	/**
	 * Creates map of request parameters from given query string
	 *
	 * @param queryString Query string to get parameters from
     * @param decodeValues Whether to decode values (which are URL-encoded)
	 * @return Map with request parameters mapped to their values
	 */
	public static Map getRequestParametersFromString(String queryString,
                                                     boolean decodeValues) {
		HashMap parameterMap = new HashMap();

		for ( int k = 0; k < queryString.length(); ) {
			int ampPos = queryString.indexOf('&', k);
			if ( ampPos == -1 ) {
				ampPos = queryString.length();
			}
			String parameter = queryString.substring(k, ampPos);
			int equalsSignPos = parameter.indexOf('=');
            if (equalsSignPos != -1) {
                String key = parameter.substring(0, equalsSignPos);
                String value = parameter.substring(equalsSignPos + 1);
                try {
                    key = URLDecoder.decode(key, Constants.DEFAULT_ENCODING);
                    if (decodeValues) {
                        value = URLDecoder.decode(value, Constants.DEFAULT_ENCODING);
                    }
                    parameterMap.put(key, mergeValues(parameterMap.get(key), value));
                } catch ( UnsupportedEncodingException e ) {
                    // do nothing
                }
            }
			k = ampPos + 1;
		}

		return parameterMap;
	}


    /**
     * Creates a map of request parameters from URI.
     *
     * @param uri An address to extract request parameters from
     * @return map of request parameters
     */
    public static Map getRequestParametersFromUri(String uri) {
        if (GenericValidator.isBlankOrNull(uri)) {
            return new HashMap();
        }

        int qSignPos = uri.indexOf('?');
        if (qSignPos == -1) {
            return new HashMap();
        }

        return RequestUtil.getRequestParametersFromString(uri.substring(qSignPos + 1));
    }


    /**
     * Extracts a base address from URI (that is, part of address before '?')
     *
     * @param uri An address to extract base address from
     * @return base address
     */
    public static String getBaseFromUri(String uri) {
        if (GenericValidator.isBlankOrNull(uri)) {
            return "";
        }

        int qSignPos = uri.indexOf('?');
        if (qSignPos == -1) {
            return uri;
        }

        return uri.substring(0, qSignPos);
    }


	/**
	 * Builds a query string from a given map of parameters
	 *
	 * @param m         A map of parameters
	 * @param ampersand String to use for ampersands (e.g. "&" or "&amp;" )
	 * @param encode    Whether or not to encode non-ASCII characters
	 * @return query string (with no leading "?")
	 */
	public static StringBuffer createQueryStringFromMap(Map m, String ampersand, boolean encode) {
		StringBuffer result = new StringBuffer("");
		Set entrySet = m.entrySet();
		Iterator entrySetIterator = entrySet.iterator();

		while ( entrySetIterator.hasNext() ) {
			Map.Entry entry = (Map.Entry) entrySetIterator.next();
			Object o = entry.getValue();

			if ( o == null ) {
				append(entry.getKey(), "", result, ampersand, encode);
			} else if ( o instanceof String ) {
				append(entry.getKey(), o, result, ampersand, encode);
			} else if ( o instanceof String[] ) {
				String[] values = (String[]) o;

				for ( int i = 0; i < values.length; i++ ) {
					append(entry.getKey(), values[i], result, ampersand, encode);
				}
			} else {
				append(entry.getKey(), o, result, ampersand, encode);
			}
		}

		return result;
	}

	/**
	 * Builds a query string from a given map of parameters
	 *
	 * @param m         A map of parameters
	 * @param ampersand String to use for ampersands (e.g. "&" or "&amp;" )
	 * @return query string (with no leading "?")
	 */
	public static StringBuffer createQueryStringFromMap(Map m, String ampersand) {
		return createQueryStringFromMap(m, ampersand, true);
	}

    /**
     * Append parameters to base URI.
     *
     * @param uri An address that is base for adding params
     * @param params A map of parameters
     * @return resulting URI
     */
    public static String appendParams(String uri, Map params) {
        String delim = (uri.indexOf('?') == -1) ? "?" : "&";
        return uri + delim + RequestUtil.createQueryStringFromMap(params, "&").toString();
    }

	/**
	 * Removes request parameters added by different grid tags
	 *
	 * @param parameterMap Parameter map to remove parameters from
	 * @return Map with additional parameters removed
	 */
	public static Map prepareParameterMap(Map parameterMap) {
		parameterMap.remove("clearFilter");
		parameterMap.remove("clearAllFilters");
		parameterMap.remove("sortField");
		parameterMap.remove("pageNumber");
		parameterMap.remove("gridName");
		return parameterMap;
	}

	/**
	 * Creates page URL from page url and request parameters specified in parent grid tag
	 *
	 * @param parentGridTag The grid tag
	 * @return URL for this grid tag
	 */
	public static String getPageUrl(GridTag parentGridTag) {
		String pageUrl = parentGridTag.getPageUrl();
		Map parameterMap = prepareParameterMap(parentGridTag.getParameterMap());
		String queryString = RequestUtil.createQueryStringFromMap(parameterMap, "&").toString();
		if ( queryString != null && queryString.length() != 0 ) {
			queryString = "?" + queryString;
		}
		pageUrl += queryString;
		return pageUrl;
	}

	/**
	 * Appends new key and value pair to query string
	 *
	 * @param key         parameter name
	 * @param value       value of parameter
	 * @param queryString existing query string
	 * @param ampersand   string to use for ampersand (e.g. "&" or "&amp;")
     * @param encode      whether to encode value
	 * @return query string (with no leading "?")
	 */
	private static StringBuffer append(Object key, Object value,
	                                   StringBuffer queryString,
	                                   String ampersand, boolean encode) {
		if ( queryString.length() > 0 ) {
			queryString.append(ampersand);
		}

		try {
			if ( encode ) {
				key = URLEncoder.encode(key.toString(), Constants.DEFAULT_ENCODING);
				value = URLEncoder.encode(value.toString(), Constants.DEFAULT_ENCODING);
			}
			queryString.append(key);
			queryString.append("=");
			queryString.append(value);
		} catch ( UnsupportedEncodingException e ) {
			// do nothing
		}
		return queryString;
	}

	/**
	 * Stores request attributes in session
	 *
	 * @param aRequest the current request
	 */
	public static void stowRequestAttributes(HttpServletRequest aRequest) {
		if ( aRequest.getSession().getAttribute(STOWED_REQUEST_ATTRIBS) != null ) {
			return;
		}

		Enumeration e = aRequest.getAttributeNames();
		Map map = new HashMap();

		while ( e.hasMoreElements() ) {
			String name = (String) e.nextElement();
			map.put(name, aRequest.getAttribute(name));
		}

		aRequest.getSession().setAttribute(STOWED_REQUEST_ATTRIBS, map);
	}

	/**
	 * Returns request attributes from session to request
	 *
	 * @param aRequest a request to which saved in session parameters will be
     * assigned
	 */
	public static void reclaimRequestAttributes(HttpServletRequest aRequest) {
		Map map =
		        (Map) aRequest.getSession().getAttribute(STOWED_REQUEST_ATTRIBS);

		if ( map == null ) {
			return;
		}

		Iterator itr = map.keySet().iterator();

		while ( itr.hasNext() ) {
			String name = (String) itr.next();
			aRequest.setAttribute(name, map.get(name));
		}

		aRequest.getSession().removeAttribute(STOWED_REQUEST_ATTRIBS);
	}

	/**
	 * Convenience method to set a cookie. The cookie gets max age set to 30 days.
	 *
	 * @param response response that will accept a cookie
	 * @param name     name of the cookie to store
	 * @param value    value of the cookie
	 * @param path     path of the cookie
	 */
	public static void setCookie(HttpServletResponse response, String name,
	                             String value, String path) {
		Log log = LogFactory.getLog(RequestUtil.class);

		if ( log.isDebugEnabled() ) {
			log.debug("Setting cookie '" + name + "' on path '" + path + "'");
		}

		Cookie cookie = new Cookie(name, value);
		cookie.setSecure(false);
		cookie.setPath(path);
		cookie.setMaxAge(3600 * 24 * 30); // 30 days

		response.addCookie(cookie);
	}

	/**
	 * Convenience method to get a cookie by name
	 *
	 * @param request the current request
	 * @param name    the name of the cookie to find
	 * @return the cookie (if found), null if not found
	 */
	public static Cookie getCookie(HttpServletRequest request, String name) {
		Cookie[] cookies = request.getCookies();
		Cookie returnCookie = null;

		if ( cookies == null ) {
			return returnCookie;
		}

		for ( int i = 0; i < cookies.length; i++ ) {
			Cookie thisCookie = cookies[i];

			if ( thisCookie.getName().equals(name) ) {
				// cookies with no value do me no good!
				if ( !thisCookie.getValue().equals("") ) {
					returnCookie = thisCookie;

					break;
				}
			}
		}

		return returnCookie;
	}

	/**
	 * Convenience method for deleting a cookie by name
	 *
	 * @param response the current web response
	 * @param cookie   the cookie to delete
	 * @param path     the path on which the cookie was set (i.e. /appfuse)
	 */
	public static void deleteCookie(HttpServletResponse response,
	                                Cookie cookie, String path) {
		if ( cookie != null ) {
			// Delete the cookie by setting its maximum age to zero
			cookie.setMaxAge(0);
			cookie.setPath(path);
			response.addCookie(cookie);
		}
	}

	/**
	 * Convenience method to get the application's URL based on request
	 * variables.
     *
     * @param request the request from which the URL is calculated
     * @return Application URL
	 */
	public static String getAppURL(HttpServletRequest request) {
		StringBuffer url = new StringBuffer();
		int port = request.getServerPort();
		if ( port < 0 ) {
			port = 80; // Work around java.net.URL bug
		}
		String scheme = request.getScheme();
		url.append(scheme);
		url.append("://");
		url.append(request.getServerName());
		if ( (scheme.equals("http") && (port != 80)) || (scheme.equals("https") && (port != 443)) ) {
			url.append(':');
			url.append(port);
		}
		url.append(request.getContextPath());
		return url.toString();
	}

	/**
	 * Merges values into one array. Instances of <code>java.lang.String</code>,
	 * <code>java.lang.String[]</code> and <code>java.util.Collection</code> are supported
	 * both in <code>v1</code> and <code>v2</code> arguments.
	 *
	 * @param v1 First object to merge
	 * @param v2 Second object to merge
	 * @return Array contains merged objects
	 */
	public static String[] mergeValues(Object v1, Object v2) {
		String[] values1 = null;
		String[] values2 = null;

		// get first array of values
		if ( v1 == null ) {
			values1 = new String[0];
		} else if ( v1 instanceof String[] ) {
			values1 = (String[]) v1;
		} else if ( v1 instanceof Collection ) {
			Collection c = (Collection) v1;
			values1 = (String[]) new ArrayList(c).toArray(new String[0]);
		} else {
			values1 = new String[]{String.valueOf(v1)};
		}

		// get second array of values
		if ( v2 == null ) {
			values2 = new String[0];
		} else if ( v2 instanceof String[] ) {
			values2 = (String[]) v2;
		} else if ( v2 instanceof Collection ) {
			Collection c = (Collection) v2;
			values2 = (String[]) new ArrayList(c).toArray(new String[0]);
		} else {
			values2 = new String[]{String.valueOf(v2)};
		}

		// merge arrays
		String[] result = new String[values1.length + values2.length];
		System.arraycopy(values1, 0, result, 0, values1.length);
		System.arraycopy(values2, 0, result, values1.length, values2.length);

		return result;
	}
}
